#coding=utf8
from random import Random

from worldexc.worldexception import (WorldException, BadDirection, 
                             BusyPlace, NoObjects, 
                             NoFreePlace, NoWay)

# направления
DIRECTION_NO = 0 # нет направления
DIRECTION_N = 1 # north
DIRECTION_S = -1 # south
DIRECTION_O = 2 # ost
DIRECTION_W = -2 # west

DIRECTIONS_STR = {
    DIRECTION_NO: 'No direction',
    DIRECTION_N: 'North',
    DIRECTION_S: 'South',
    DIRECTION_O: 'East',
    DIRECTION_W: 'West'
}

class World(object):
    '''Объект Мира
    '''
    def __init__(self, width, height):
        self.width = width
        self.height = height
        self.objects = [] # список акторов
        self.deads = [] # список мертвецов
        self.children = [] # список детей
        self.classes = set() # множество используемых классов объектов
        self._coords = {} # список координат объектов (coords[(x,y)] -> object)
        self._distances = {} # словарь расстояний м.у. объектами
    
    def init_object(self, obj, parent=None):
        '''Инициация объекта в мире.
        '''
        # подбор координат и направления
        xy = obj.get_coords()
        if obj.get_coords() == (None, None):
            if parent is None:
                xy = self.find_free_place()
            else:
                xy = self.find_free_place_near(parent.get_coords())
            obj.set_coords(xy)
            
        if obj.get_direction() is None:
            obj.set_direction(self.get_random_direction())
        
        # добавляем объект и координаты и расстояния
        self.objects.append(obj)
        self._coords[xy] = obj
        self._distances[xy] = {}
        self.calculate_object_distances(obj)
        # регистрируем класс в мире
        self.classes.add(self.__class__)
    
    def remove_object(self, obj):
        xy = obj.get_coords()
        self.objects.remove(obj)
        #print self._distances
        self._distances.pop(xy)
        #print self._coords
        self._coords.pop(xy)
            
    
    def abs_get_object(self, xy):
        '''Возвращает объект расположенный в абсолютных координатах (x, y). 
        Если объект отсутствует, то возвращает None.
        '''
        xy = self.trunc_coords(xy)
        return self._coords.get(xy, None)
    
    def rel_get_object(self, xy0, xyr, direction):
        '''Возвращает объект, расположенный в координатах (xr, yr) 
        относительно объекта в  (x0, y0) ориентированного в направлении direction'''
        xy = self.rel2abs(xy0, xyr, direction)
        self.abs_get_object(xy)
    
    def abs_move_object(self, obj, xy):
        '''Перемещаем объект obj в xy
        '''
        if obj is None:
            return
        if not self.is_free(xy):
            raise BusyPlace(xy)
        xy0 = obj.get_coords()
        obj.set_coords(xy)
        self._coords[xy] = obj
        del self._coords[xy0] 
        del self._distances[xy0]
        self.calculate_object_distances(obj)
        
        
    def rel_move_object(self, obj, xyr):
        '''Перемещение объекта в относительную точку xy
        '''
        xy0 = obj.get_coords()
        direction = obj.get_direction()
        xy = self.rel2abs(xy0, xyr, direction)
        self.abs_move_object(obj, xy)
        
    def direct_way_exists(self, xy, direction, distance=1):
        '''Проверяет, есть ли прямой маршрут из точки xy в 
        направлении direction на расстоянии distance.
        Если маршрут есть, то возращает True
        '''
        if direction == DIRECTION_N:
            inc = lambda xy, d: (xy[0], xy[1] + d)
        elif direction == DIRECTION_S:
            inc = lambda xy, d: (xy[0], xy[1] - d)
        elif direction == DIRECTION_W:
            inc = lambda xy, d: (xy[0] - d, xy[1])
        elif direction == DIRECTION_O:
            inc = lambda xy, d: (xy[0] + d, xy[1])
        else:
            raise BadDirection(direction)
        for d in xrange(1, distance + 1):
            if not self.is_free(inc(xy, d)):
                return False
        else:
            return True
    
    def move_object_direct(self, obj, distance=1):
        '''Переместить объект по прямой в заданном направлении 
        на расстояние    distance
        '''
        xy = obj.get_coords()
        direction = obj.get_direction()
        if self.direct_way_exists(xy, direction, distance):
            self.abs_move_object(obj, self.inc_coords(xy, direction, distance))
        else:
            raise NoWay(xy, self.inc_coords(xy, direction, distance))
            
    def is_free(self, xy):
        '''Возвращает True, если координаты пустые.
        '''
        return self.abs_get_object(xy) is None
    
    def find_free_place(self):
        '''Ищет случайное пустое место. 
        Если найти за определенное число попыток не удалось найти - 
        возбуждает NoFreePlace. Число попыток определяется половиной суммы
        высоты и ширины.
        '''
        r = Random()
        if len(self.objects) == self.width * self.height:
            raise NoFreePlace()
        for counter in xrange((self.width + self.height) / 2):
            x = r.randint(0, self.width - 1)
            y = r.randint(0, self.height - 1)
            if self.is_free((x, y)): 
                return x, y
        else:
            raise NoFreePlace()
    
    def find_free_place_near(self, xy):
        '''Выполняет поиск свободного места в радиусе одной клетки вокруг
        xy. Если место не найдено - возбуждает NoFreePlace.
        '''
        x, y = xy
        for dx in (-1, 0, 1):
            for dy in (-1, 0, 1):
                if dx == 0 and dy == 0: 
                    continue
                if self.is_free((x + dx, y + dy)):
                    return x + dx, y + dy
        else:
            raise NoFreePlace()
    
    def get_random_direction(self):
        '''Возвращает случайное направление.
        '''
        return Random().choice((DIRECTION_N, DIRECTION_S, 
                                DIRECTION_W, DIRECTION_O))
        
    def calculate_object_distances(self, obj):
        '''Рассчитывает расстояния м.у. объектом и всем остальными объектами мира.
        '''
        xy0 = obj.get_coords()
        self._distances[xy0] = {}
        for xy1 in self._coords:
            dist = self.distance(xy0, xy1)
            self._distances[xy0][xy1] = dist
            self._distances[xy1][xy0] = dist  
    
    def distance(self, xy1, xy2):
        '''Возвращает расстояние м.у. объектами xy1 и xy2.
        '''
        x1, y1 = xy1
        x2, y2 = xy2
        return (x1 - x2) ** 2 + (y1 - y2) ** 2 
    
    def inc_coords(self, xy, direction, value=1):
        '''Увеличивает координаты xy в направлении direction на value.
        '''
        x, y = xy
        if direction == DIRECTION_N:
            xy = x, y + value
        elif direction == DIRECTION_S:
            xy = x, y - value
        elif direction == DIRECTION_W:
            xy = x - 1, y
        elif direction == DIRECTION_O:
            xy = x + 1, y
        else:
            raise BadDirection(direction)
        return self.trunc_coords(xy)
        
    def trunc_coords(self, xy, width=-1, height=-1):
        '''Обрезает координаты, выходящие за границы карты.
        '''
        x, y = xy
        if width <= 0:
            width = self.width
        if height <= 0:
            height = self.height
        x %= width
        y %= height
        if x < 0:
            x += width
        if y < 0:
            y += height
        return x, y
        
    def rel2abs(self, xy0, xyr, direction):
        '''Преобразовывает координаты xr,yr в абсолютные относительно x0,y0
        и в зависимости от направления direction. Возвращает кортеж (x, y)
        '''
        x0, y0 = self.trunc_coords(xy0)
        xr, yr = xyr
        if direction == DIRECTION_N:
            return x0 + xr, y0 + yr 
        elif direction == DIRECTION_S:
            return x0 - xr, y0 - yr
        elif direction == DIRECTION_O:
            return x0 + yr, y0 - xr
        elif direction == DIRECTION_W:
            return x0 - yr, y0 + xr
        else:
            raise BadDirection(direction)
        
    def abs2rel(self, xy0, xy, direction):
        '''Преобразовывает абсолютные координаты объекта x, y в относительные
        координаты относительно x0, y0 в зависимости от направления direction.
        Возвращает кортеж (x, y).
        '''
        x0, y0 = xy0
        x, y = xy
        if direction == DIRECTION_N:
            xy = x - x0, y - y0 
        elif direction == DIRECTION_S:
            xy = x0 - x, y0 - y
        elif direction == DIRECTION_O:
            xy = y0 - y, x - x0
        elif direction == DIRECTION_W:
            xy = y - y0, x0 - x
        else:
            raise BadDirection(direction)
        return self.trunc_coords(xy)
    
    def left_direction(self, direction):
        '''Направление на левый траверз
        '''
        if direction == DIRECTION_N:
            return DIRECTION_W
        elif direction == DIRECTION_W:
            return DIRECTION_S
        elif direction == DIRECTION_S:
            return DIRECTION_O
        elif direction == DIRECTION_O:
            return DIRECTION_N
        else:
            raise BadDirection(direction)
    
    def right_direction(self, direction):
        '''Направление на правый траверз.
        '''
        if direction == DIRECTION_N:
            return DIRECTION_O
        elif direction == DIRECTION_W:
            return DIRECTION_N
        elif direction == DIRECTION_S:
            return DIRECTION_W
        elif direction == DIRECTION_O:
            return DIRECTION_S
        else:
            raise BadDirection(direction)
    
    def back_direction(self, direction):
        '''Обратное  направление.
        '''
        if direction == DIRECTION_N:
            return DIRECTION_S
        elif direction == DIRECTION_W:
            return DIRECTION_O
        elif direction == DIRECTION_S:
            return DIRECTION_N
        elif direction == DIRECTION_O:
            return DIRECTION_W
        else:
            raise BadDirection(direction)   

    def direction2str(self, direction):
        '''Вывод направления в виде строки.
        '''
        if direction == DIRECTION_N:
            return 'North'
        elif direction == DIRECTION_W:
            return 'South'
        elif direction == DIRECTION_S:
            return DIRECTION_N
        elif direction == DIRECTION_O:
            return DIRECTION_W
        else:
            raise BadDirection(direction)   
     
    

    def __getitem__(self, item):
        return self._coords.get(item, None)
    
    def __repr__(self):
        res = 'World Size: (%s, %s)\n' % (self.width, self.height)
        for x in xrange(self.width):
            res += '-' * (self.width * 2 + 1)
            res += '\n'
            for y in xrange(self.height-1, -1, -1):
                obj = self.abs_get_object((x, y))
                if obj is None:
                    res += '| '
                else:
                    res += '|%s' % obj.id
            res += '|\n'
        res += '-' * (self.width * 2 + 1)
        res += '\n'
        return res


if __name__ == '__main__':
    
    from actors.worldthing import WorldThing
    
    w = World(3, 3)
    obj = WorldThing(id=0, helth=10, max_helth=10, 
                 world=w)
    w.init_object(obj)
    print w